package com.test.redis.service;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.stereotype.Service;
import java.time.Duration;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.util.*;
import java.util.stream.Collectors;
import java.time.LocalDateTime;

/**
 * 基于Redis Bitmap的会议室预定服务（改进版）
 * 使用15分钟作为时间粒度，减少内存占用并提高性能
 */
@Service
@Slf4j
public class ReservationBitMapService {
    
    private final RedisTemplate<String, Object> redisTemplate;
    
    // 使用15分钟作为时间粒度，一天96个时间片（24 * 4）
    private static final int TIME_SLOT_MINUTES = 15;
    private static final int SLOTS_PER_DAY = 24 * 60 / TIME_SLOT_MINUTES; // 96个时间片
    
    private static final String RESERVATION_BITMAP_KEY = "reservation:room:%d:date:%s";
    
    public ReservationBitMapService(RedisTemplate<String, Object> redisTemplate) {
        this.redisTemplate = redisTemplate;
    }
    
    /**
     * 检查时间段是否可用（使用15分钟粒度）
     */
    public boolean isTimeSlotAvailable(Long roomId, LocalDateTime startTime, LocalDateTime endTime) {
        String dateStr = startTime.toLocalDate().toString();
        String key = String.format(RESERVATION_BITMAP_KEY, roomId, dateStr);
        
        int startSlot = timeToSlotOffset(startTime);
        int endSlot = timeToSlotOffset(endTime);
        
        // 如果结束时间不是时间片的整数倍，需要包含下一个时间片
        if (endTime.getMinute() % TIME_SLOT_MINUTES != 0 || endTime.getSecond() != 0 || endTime.getNano() != 0) {
            endSlot += 1;
        }
        
        log.debug("检查时间段可用性: roomId={}, slots={}~{}", roomId, startSlot, endSlot);
        
        // 检查时间段内是否有已被预定的位
        for (int i = startSlot; i < endSlot; i++) {
            Boolean isBooked = redisTemplate.opsForValue().getBit(key, i);
            if (Boolean.TRUE.equals(isBooked)) {
                log.debug("时间段冲突: slot={} 已被预定", i);
                return false;
            }
        }
        return true;
    }
    
    /**
     * 预定时间段（使用15分钟粒度）
     */
    public boolean reserveTimeSlot(Long roomId, LocalDateTime startTime, LocalDateTime endTime, String reservationId) {
        String dateStr = startTime.toLocalDate().toString();
        String key = String.format(RESERVATION_BITMAP_KEY, roomId, dateStr);
        
        int startSlot = timeToSlotOffset(startTime);
        int endSlot = timeToSlotOffset(endTime);
        
        // 如果结束时间不是时间片的整数倍，需要包含下一个时间片
        if (endTime.getMinute() % TIME_SLOT_MINUTES != 0 || endTime.getSecond() != 0 || endTime.getNano() != 0) {
            endSlot += 1;
        }
        
        log.debug("预定时间段: roomId={}, slots={}~{}", roomId, startSlot, endSlot);
        
        // 使用Lua脚本确保原子性操作
        String luaScript = """
            local key = KEYS[1]
            local startSlot = tonumber(ARGV[1])
            local endSlot = tonumber(ARGV[2])
            
            -- 检查所有时间片是否都可用
            for i = startSlot, endSlot - 1 do
                if redis.call('GETBIT', key, i) == 1 then
                    return 0
                end
            end
            
            -- 设置所有时间片为已预定
            for i = startSlot, endSlot - 1 do
                redis.call('SETBIT', key, i, 1)
            end
            
            return 1
        """;
        
        DefaultRedisScript<Long> redisScript = new DefaultRedisScript<>();
        redisScript.setScriptText(luaScript);
        redisScript.setResultType(Long.class);
        
        Long result = redisTemplate.execute(redisScript, 
            Collections.singletonList(key),
            String.valueOf(startSlot), 
            String.valueOf(endSlot));
        
        return result != null && result == 1;
    }
    
    /**
     * 取消预定
     */
    public void cancelTimeSlot(Long roomId, LocalDateTime startTime, LocalDateTime endTime) {
        String dateStr = startTime.toLocalDate().toString();
        String key = String.format(RESERVATION_BITMAP_KEY, roomId, dateStr);
        
        int startSlot = timeToSlotOffset(startTime);
        int endSlot = timeToSlotOffset(endTime);
        
        // 如果结束时间不是时间片的整数倍，需要包含下一个时间片
        if (endTime.getMinute() % TIME_SLOT_MINUTES != 0 || endTime.getSecond() != 0 || endTime.getNano() != 0) {
            endSlot += 1;
        }
        
        log.debug("取消预定时间段: roomId={}, slots={}~{}", roomId, startSlot, endSlot);
        
        // 清除位图
        for (int i = startSlot; i < endSlot; i++) {
            redisTemplate.opsForValue().setBit(key, i, false);
        }
    }
    
    /**
     * 将时间转换为当天时间片的偏移量（15分钟粒度）
     */
    private int timeToSlotOffset(LocalDateTime dateTime) {
        int totalMinutes = dateTime.getHour() * 60 + dateTime.getMinute();
        return totalMinutes / TIME_SLOT_MINUTES;
    }
    
    /**
     * 将时间片偏移量转换回时间
     */
    private LocalDateTime slotToTime(int slot, LocalDate date) {
        int totalMinutes = slot * TIME_SLOT_MINUTES;
        int hours = totalMinutes / 60;
        int minutes = totalMinutes % 60;
        return LocalDateTime.of(date, LocalTime.of(hours, minutes));
    }
    
    /**
     * 获取会议室某天的预定情况
     */
    public List<TimeSlot> getDailyReservations(Long roomId, LocalDate date) {
        String key = String.format(RESERVATION_BITMAP_KEY, roomId, date.toString());
        List<TimeSlot> timeSlots = new ArrayList<>();
        
        // 获取所有时间片的预定状态
        List<Boolean> slotStatus = new ArrayList<>();
        for (int i = 0; i < SLOTS_PER_DAY; i++) {
            Boolean isBooked = redisTemplate.opsForValue().getBit(key, i);
            slotStatus.add(Boolean.TRUE.equals(isBooked));
        }
        
        // 将连续的预定时间段合并
        int start = -1;
        for (int i = 0; i < SLOTS_PER_DAY; i++) {
            boolean isBooked = slotStatus.get(i);
            if (isBooked && start == -1) {
                start = i;
            } else if (!isBooked && start != -1) {
                timeSlots.add(new TimeSlot(
                    slotToTime(start, date),
                    slotToTime(i, date)
                ));
                start = -1;
            }
        }
        
        if (start != -1) {
            timeSlots.add(new TimeSlot(
                slotToTime(start, date),
                slotToTime(SLOTS_PER_DAY, date)
            ));
        }
        
        return timeSlots;
    }
    
    /**
     * 获取会议室某天的可用时间段
     */
    public List<TimeSlot> getDailyAvailableSlots(Long roomId, LocalDate date) {
        List<TimeSlot> reservedSlots = getDailyReservations(roomId, date);
        List<TimeSlot> availableSlots = new ArrayList<>();
        
        LocalDateTime dayStart = LocalDateTime.of(date, LocalTime.MIN);
        LocalDateTime dayEnd = LocalDateTime.of(date, LocalTime.MAX);
        
        if (reservedSlots.isEmpty()) {
            availableSlots.add(new TimeSlot(dayStart, dayEnd));
        } else {
            // 添加第一个可用时间段
            if (reservedSlots.get(0).getStartTime().isAfter(dayStart)) {
                availableSlots.add(new TimeSlot(dayStart, reservedSlots.get(0).getStartTime()));
            }
            
            // 添加中间的空闲时间段
            for (int i = 0; i < reservedSlots.size() - 1; i++) {
                availableSlots.add(new TimeSlot(
                    reservedSlots.get(i).getEndTime(),
                    reservedSlots.get(i + 1).getStartTime()
                ));
            }
            
            // 添加最后一个可用时间段
            if (reservedSlots.get(reservedSlots.size() - 1).getEndTime().isBefore(dayEnd)) {
                availableSlots.add(new TimeSlot(
                    reservedSlots.get(reservedSlots.size() - 1).getEndTime(),
                    dayEnd
                ));
            }
        }
        
        return availableSlots;
    }
    
    /**
     * 批量检查多个会议室的时间段可用性
     */
    public Map<Long, Boolean> batchCheckAvailability(List<Long> roomIds, LocalDateTime startTime, LocalDateTime endTime) {
        Map<Long, Boolean> result = new HashMap<>();
        
        for (Long roomId : roomIds) {
            boolean available = isTimeSlotAvailable(roomId, startTime, endTime);
            result.put(roomId, available);
        }
        
        return result;
    }
    
    /**
     * 清理过期的bitmap数据（保留最近7天）
     */
    public void cleanupExpiredData() {
        LocalDate sevenDaysAgo = LocalDate.now().minusDays(7);
        String pattern = "reservation:room:*:date:*";
        
        Set<String> keys = redisTemplate.keys(pattern);
        if (keys != null) {
            for (String key : keys) {
                // 从key中提取日期
                String[] parts = key.split(":");
                if (parts.length >= 5) {
                    String dateStr = parts[4];
                    try {
                        LocalDate date = LocalDate.parse(dateStr);
                        if (date.isBefore(sevenDaysAgo)) {
                            redisTemplate.delete(key);
                            log.info("清理过期bitmap数据: {}", key);
                        }
                    } catch (Exception e) {
                        log.warn("解析bitmap key日期失败: {}", key, e);
                    }
                }
            }
        }
    }
    
    @Data
    @AllArgsConstructor
    public static class TimeSlot {
        private LocalDateTime startTime;
        private LocalDateTime endTime;
    }
}